package com.gzsxy.esjy.gateway.filter;

import com.gzsxy.esjy.common.base.util.UriUtil;
import com.gzsxy.esjy.gateway.config.properties.GateWayProperties;
import io.netty.buffer.PooledByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.UUID;

/**
 * 全局日志过滤器
 */
@Slf4j
@Component
public class LogWebFilter implements WebFilter {

    private static final Logger LOG = LoggerFactory.getLogger(LogWebFilter.class);

    private static final String REQUEST_ID = "requestId";

    private static final String CLIENT_IP = "clientIp";

    private static final String START_TIME = "startTime";



    @Autowired
    private GateWayProperties properties;

    @Value("${is.valid.license:true}")
    private boolean isLicense;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {

        if (isLicense){

        }
        exchange.getAttributes().put(START_TIME, System.currentTimeMillis());

        ServerHttpRequest request = exchange.getRequest();
        //记录请求uuid
        final String uuid = getRequestId(request);
        final String ipAddress = getIpAddress(request);

        exchange.getAttributes().put(REQUEST_ID, uuid);
        exchange.getAttributes().put(CLIENT_IP, ipAddress);

        //获取真实路径
        String uri = request.getURI().getPath();

        LogWebFilter.RecorderServerHttpRequestDecorator requestDecorator = null;
        GateWayProperties.LogProperties.Log requestLog = properties.getLog().getRequest();
        GateWayProperties.LogProperties.Log responseLog = properties.getLog().getResponse();
        String paths = null;
        // 1、判断是否打印请求日志
        if (requestLog.isEnabled()){
            paths = requestLog.getPaths();
            // 校验请求url
            if (checkLog(paths.split(","),uri)){
               requestDecorator = new LogWebFilter.RecorderServerHttpRequestDecorator(request,uuid,ipAddress,requestLog);
            }
        }
        // 增加头部信息打印
        ServerHttpRequest newRequest = (requestDecorator == null ? request : requestDecorator).mutate()
                .header(REQUEST_ID, uuid).header(CLIENT_IP, ipAddress).build();
        Mono<Void> filter = null;
        // 2、判断是否需要打印应答日志
        if (responseLog.isEnabled()) {
            paths = responseLog.getPaths();
            if (checkLog(paths.split(","), uri)) {
                filter = chain.filter(exchange.mutate().request(newRequest)
                        .response(new RecordServerHttpResponseDecorator(exchange, uuid, responseLog)).build());
            }
        }
        if (filter == null) {
            filter = chain.filter(exchange.mutate().request(newRequest).build());
        }
        // 统计请求时间
        if (properties.getLog().isConsuTime()) {
            filter.then(Mono.fromRunnable(() -> {
                Long startTime = exchange.getAttribute(START_TIME);
                String clientIp = exchange.getAttribute(CLIENT_IP);
                String requestId = exchange.getAttribute(REQUEST_ID);
                if (startTime != null) {
                    Long executeTime = (System.currentTimeMillis() - startTime);
                    int code = exchange.getResponse().getStatusCode() != null
                            ? exchange.getResponse().getStatusCode().value() : 500;
                    // 当前仅记录日志，后续可以添加日志队列，来过滤请求慢的接口
                    LOG.info("[{}] 来自IP地址：{}的请求接口：{}，响应状态码：{}，请求耗时：{}ms", requestId, clientIp, code, executeTime);
                }
            }));
        }

        return filter;
    }


    /**
     * 获取请求id
     * @param request
     * @return
     */
    private String getRequestId(ServerHttpRequest request) {
        String requestId = request.getQueryParams().getFirst(REQUEST_ID);
        if (!StringUtils.hasLength(requestId)) {
            requestId = request.getHeaders().getFirst(REQUEST_ID);
        }
        if (requestId == null) {
            requestId = UUID.randomUUID().toString();
        }
        return requestId;
    }


    /**
     * 获取真实ip
     * @param request
     * @return
     */
    private static String getIpAddress(ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        String ip = headers.getFirst("x-forwarded-for");
        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值，第一个ip才是真实ip
            if (ip.contains(",")) {
                ip = ip.split(",")[0];
            }
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddress() == null ? "unknown"
                    : request.getRemoteAddress().getAddress().getHostAddress();
        }
        return ip;
    }

    /**
     * 获取
     * @param dataBuffer
     * @return
     */
    protected static byte[] getContent(DataBuffer dataBuffer) {
        byte[] content = new byte[dataBuffer.readableByteCount()];
        // 标记位置
        int i = dataBuffer.readPosition();
        dataBuffer.read(content);
        // 还原读取位置
        dataBuffer.readPosition(i);
        return content;
    }


    /**
     * 判断当前uri是否被要求日志输出
     * @param strs
     * @param uri
     * @return
     */
    protected boolean checkLog(String[] strs, String uri) {
        return Arrays.asList(strs).stream().anyMatch((pattern) -> UriUtil.isUrlMatches(pattern, uri));
    }


    /**
     * 重写请求
     */
    public static class RecorderServerHttpRequestDecorator extends ServerHttpRequestDecorator {

        /**
         * 报文编码
         */
        private String charsetName;

        /**
         * 请求id
         */
        private String requestId;

        private boolean logBody = true;

        private DataBufferFactory dataBufferFactory;

        public RecorderServerHttpRequestDecorator(ServerHttpRequest delegate, String uuid,String clientIp,
                                                  GateWayProperties.LogProperties.Log requestLog) {
            super(delegate);
            MediaType contentType = this.getHeaders().getContentType();
            this.requestId = uuid;
            if (this.getMethod() != HttpMethod.POST || this.getMethod() != HttpMethod.PUT
                    || this.getMethod() != HttpMethod.PATCH) {
                setLogBody(requestLog);
            }
            charsetName(contentType);

            HttpMethod method = this.getMethod();
            if (method == null) {
                method = HttpMethod.GET;
            }

            LOG.info("Request[{}] url={} {} {}; address={}; header={}", requestId, delegate.getURI().getRawPath(),
                    delegate.getURI().getRawQuery(), method.name(), clientIp, this.getHeaders());
            this.dataBufferFactory = new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT);
        }

        /**
         * 设置请求内容打印
         * @param requestLog
         */
        private void setLogBody(GateWayProperties.LogProperties.Log requestLog) {
            long contentLength = this.getHeaders().getContentLength();
            if (contentLength < 0) {
                logBody = false;
                return;
            }
            if (requestLog.getMaxBody() != 0) {
                if (contentLength > requestLog.getMaxBody()) {
                    LOG.warn("RequestBody[{}] 请求内容过大", requestId);
                    logBody = false;
                    return;
                }
            }
            MediaType contentType = this.getHeaders().getContentType();
            if (contentType == null) {
                logBody = false;
                return;
            }
            String contentTypes = requestLog.getContentTypes();
            if (contentTypes != null) {
                logBody = Arrays.asList(contentTypes.split(",")).stream()
                        .anyMatch(type -> contentType.includes(MediaType.valueOf(type)));
            }
        }

        /**
         * 设置编码名称
         * @param contentType
         */
        private void charsetName(MediaType contentType) {
            Charset charset = contentType == null ? null : contentType.getCharset();
            this.charsetName = charset == null ? System.getProperty("file.encoding") : charset.name();
        }

        @Override
        public Flux<DataBuffer> getBody() {
            if (logBody) {
                return super.getBody().map(dataBuffer -> {
                    byte[] content = getContent(dataBuffer);
                    String responseResult = new String(content, Charset.forName(charsetName));
                    LOG.info("RequestBody[{}] {}", requestId, responseResult);
                    return dataBuffer;
                });
            }
            return super.getBody();
        }
    }



    /**
     * 响应
     */
    public static class RecordServerHttpResponseDecorator extends ServerHttpResponseDecorator {

        private String charsetName;

        private boolean logBody = false;

        private final String requestId;

        private final GateWayProperties.LogProperties.Log log;

        public RecordServerHttpResponseDecorator(ServerWebExchange delegate, String uuid,
                                                 GateWayProperties.LogProperties.Log responsetLog) {
            super(delegate.getResponse());
            this.requestId = uuid;
            this.log = responsetLog;
        }

        @Override
        public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
            MediaType mediaType = this.getHeaders().getContentType();
            charsetName(mediaType);
            HttpStatus statusCode = this.getStatusCode();
            if (statusCode != HttpStatus.OK) {
                LOG.error("Response[{}] status={} header={}", this.requestId, statusCode, this.getHeaders());
            }
            else {
                LOG.info("Response[{}] status={} header={}", this.requestId, statusCode, this.getHeaders());
            }
            setLogBody(log);
            if (body instanceof Flux) {
                Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                if (logBody) {
                    return super.writeWith(fluxBody.map(dataBuffer -> {
                        // probably should reuse buffers
                        byte[] content = getContent(dataBuffer);
                        String responseResult = new String(content, Charset.forName(charsetName));
                        LOG.info("ResponseBody[{}] {}", this.requestId, responseResult);
                        return dataBuffer;
                    }));
                }
            }
            return super.writeWith(body);
        }

        /**
         * 设置请求内容打印
         * @param requestLog
         */
        private void setLogBody(GateWayProperties.LogProperties.Log requestLog) {

            MediaType contentType = this.getHeaders().getContentType();
            if (contentType == null) {
                logBody = true;
                return;
            }
            String contentTypes = requestLog.getContentTypes();
            if (contentTypes != null) {
                logBody = Arrays.asList(contentTypes.split(",")).stream()
                        .anyMatch(type -> contentType.includes(MediaType.valueOf(type)));
            }
        }

        /**
         * 设置编码名称
         * @param contentType
         */
        private void charsetName(MediaType contentType) {
            Charset charset = contentType == null ? null : contentType.getCharset();
            this.charsetName = charset == null ? System.getProperty("file.encoding") : charset.name();
        }

    }




}



